﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Timers;
using SQLPXBase;
using Timer = System.Timers.Timer;

namespace GianosNET.Common.Classes
{
    public abstract class LoopingService : RemotedClass
    {
        protected Timer autoRestartTmr;

        // Impersonation credentials
        private bool useImpersonation = false;
        private string domain;
        private string username;
        private string password;

        #region Costruttori

        protected LoopingService(bool enableAutoStart, ServerInfo info) 
            : this("", "", new Hashtable(), null, enableAutoStart, info) { }
        protected LoopingService(string svcName, string version, Hashtable config, ProxyManager<sqlStatus> statusProxyMgr, bool enableAutoStart, ServerInfo info)
            : base(svcName, version, config, statusProxyMgr, enableAutoStart, info)
        {
            // Setta la pausa fra i loop
            LoopingPause = 300; // Pausa di default = 5 minuti
            int tmp;
            if (config.ContainsKey("LoopingPause"))
                if (int.TryParse(config["LoopingPause"] as string, out tmp))
                    LoopingPause = tmp;


            // Imposta il timer di autoRestart del servizio al verificarsi si un errore
            if (config.ContainsKey("autoRestartAfter") && int.TryParse(config["autoRestartAfter"] as string, out tmp))
            {
                forceAutoRestartAfter = tmp;

                autoRestartTmr = new Timer(tmp*1000);
                autoRestartTmr.Elapsed += autoRestartTmr_Elapsed;
            }

            if (config.ContainsKey("RunAs"))
            {
                string[] credentials = (config["RunAs"] as string ?? "").Split(';');
                if (credentials.Length == 3)
                {
                    domain = credentials[0];
                    username = credentials[1];
                    password = credentials[2];
                    useImpersonation = true;
                }
            }
        }

        #endregion

        public enum LoopingStateEnum
        {
            Stopped,
            Initializing,
            Running,
            Idle
        }

        protected bool FirstRun { get; private set; }
        public virtual int LoopingPause { get; set; }

        private static string DefaultConfigArea { get { return ""; } }

        private LoopingStateEnum loopingState;
        public LoopingStateEnum LoopingState
        {
            get { return loopingState; }
            protected set
            {
                if (loopingState == value)
                    return;

                loopingState = value;

                // Propaga l'evento ai client
                ServerInfo.PublishLoopingStatusChange(UniqueIdentifier, value);
            }
        }

        public int SecsBeforeNextExecution { get; protected set; }

        protected DateTime? onErrorSince { get; set; }
        protected int? forceAutoRestartAfter { get; private set; }

        public void ForceRestart()
        {
            ForceRestart(0);
        }
        public void ForceRestart(int secsToWait)
        {
            if (LoopingState == LoopingStateEnum.Idle && SecsBeforeNextExecution > 0)
                SecsBeforeNextExecution = Math.Min(secsToWait, SecsBeforeNextExecution);
        }

        void autoRestartTmr_Elapsed(object sender, ElapsedEventArgs e)
        {
            ServerInfo.EnqueueInLog(MainLogId,
                                    string.Format("Tentativo di riavvio automatico del servizio: {0}", DateTime.Now));

            onErrorSince = null;
            if (autoRestartTmr != null)
                autoRestartTmr.Stop();

            // if the service is a Scheduled, checks if immediately execute job after restart
            if (this is ScheduledService)
            {
                ScheduledService tmp = (ScheduledService) this;
                if (tmp.ImmediateExecuteAfterException)
                    tmp.ImmediateExecute = true;
            }

            Start();
        }

        public override void Stop()
        {
            base.Stop();

            if (autoRestartTmr != null)
                autoRestartTmr.Stop();
        }

        public override RemotedClassStatusEnum Status
        {
            get { return base.Status; }
            set
            {
                if (status == value)
                    return;

                status = value;

                // Propaga l'evento ai client
                ServerInfo.PublishServiceStatusChange(UniqueIdentifier, value);

                // Segnala l'evento via SQL
                if (statusProxyMgr != null)
                    switch (value)
                    {
                        case RemotedClassStatusEnum.Error:
                            if (forceAutoRestartAfter.HasValue && forceAutoRestartAfter.Value >= 0)
                                statusProxyMgr.GetProxy().NotifyServiceStatus(ServiceName, "SUSPENDED");
                            else
                                statusProxyMgr.GetProxy().NotifyServiceStatus(ServiceName, "ERROR");
                            break;
                        case RemotedClassStatusEnum.Stopped:
                            statusProxyMgr.GetProxy().NotifyServiceStatus(ServiceName, "STOPPED");
                            break;
                        case RemotedClassStatusEnum.Started:
                            statusProxyMgr.GetProxy().NotifyServiceStatus(ServiceName, "OK");
                            break;
                    }
            }
        }


        public override BackgroundWorker GetServerJob()
        {
            BackgroundWorker job = new BackgroundWorker()
            {
                WorkerReportsProgress = true,
                WorkerSupportsCancellation = true
            };

            job.DoWork += JobDoWorkStandardHandler;
            job.RunWorkerCompleted += JobRunWorkerCompletedStandardHandler;

            return job;
        }

        protected virtual void JobRunWorkerCompletedStandardHandler(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                ServerInfo.EnqueueExceptionInLog(this, e.Error);

                if (HasLogger) DoLog(LogTypeEnum.Error, string.Format("{0} error", ServiceName), e.Error);
                Status = RemotedClassStatusEnum.Error;

                if (forceAutoRestartAfter.HasValue)
                {
                    autoRestartTmr.Start();
                    ServerInfo.EnqueueInLog(MainLogId, string.Format("Autorestart impostato fra {0} secondi.", forceAutoRestartAfter.Value));

                    if (HasLogger)
                        DoLog(LogTypeEnum.Warning,
                              string.Format("{1}: autorestarting in {0} secs.", forceAutoRestartAfter.Value, ServiceName));
                }
            }

            LoopingState = LoopingStateEnum.Stopped;
            //Status = RemotedClassStatusEnum.Stopped;

            if (HasLogger)
                DoLog(LogTypeEnum.Info, string.Format("{0} stopped", ServiceName));
            ServerInfo.EnqueueInLog(MainLogId, "** Servizio sospeso **");
        }

        protected virtual void JobDoWorkStandardHandler(object sender, DoWorkEventArgs e)
        {
            if (HasLogger) DoLog(LogTypeEnum.Info, string.Format("{0} started", ServiceName));

            // Setta il servizio come running
            LoopingState = LoopingStateEnum.Initializing;
            SecsBeforeNextExecution = 0;

            #region Controllo configurazione

            BackgroundWorker job = sender as BackgroundWorker;
            if (job == null) return;

            CustomGetConfiguration();

            #endregion

            FirstRun = true;
            while (!job.CancellationPending)
            {
                // Setta il servizio come running
                LoopingState = LoopingStateEnum.Running;
                SecsBeforeNextExecution = 0;

                bool doRun = false;

                if (this is ScheduledService)
                {
                    // skips first execution on scheduled services if !ImmediateExecute
                    if (!FirstRun || ((ScheduledService) this).ImmediateExecute)
                        doRun = true;
                }
                else
                    doRun = true;

                if (doRun)
                    if (useImpersonation)
                    {
                        using (Impersonator impersonator = new Impersonator(username, domain, password))
                            CustomJobExecution(job);
                    }
                    else
                        CustomJobExecution(job);

                FirstRun = false;

                if (this is ScheduledService)
                {
                    //ServerInfo.GetLog(MainLogId).

                    ServerInfo.EnqueueInLog(MainLogId,
                        string.Format("-------- sleeping till {0:g} -------", ((ScheduledService) this).NextOccurence));
                }
                else
                {
                    LogQueue l = ServerInfo.GetLog(MainLogId);
                    string last = l.Count > 0 ? l.Last() as string : "";
                    if (string.IsNullOrEmpty(last) || !last.Contains("-- sleeping --"))
                        ServerInfo.EnqueueInLog(MainLogId, "--------------------- sleeping ---------------------");
                }

                // Forza la pulizia delle risorse inutilizzate
                GC.Collect();

                // .. e si mette in idle
                LoopingState = LoopingStateEnum.Idle;
                SecsBeforeNextExecution = LoopingPause;
                while (!job.CancellationPending && SecsBeforeNextExecution > 0)
                {
                    if (LoopingPause < SecsBeforeNextExecution)
                        SecsBeforeNextExecution = LoopingPause;
                    Thread.Sleep(1000);
                    ServerInfo.PublishIdleCountdown(UniqueIdentifier, SecsBeforeNextExecution);

                    SecsBeforeNextExecution--;
                }

            }

        }

        protected virtual void CustomGetConfiguration()
        {
            
        }

        protected abstract void CustomJobExecution(BackgroundWorker job);

    }
}
